package main

import (
	"encoding/json"
	"fmt"
	"log"
	"net/http"
	"time"

	"github.com/justinas/alice"

	jsoniter "github.com/json-iterator/go"

	"gopkg.in/validator.v2"

	"github.com/gorilla/mux"
)

//App encapsulates Env, outer and Middleware
type App struct {
	Router      *mux.Router
	Middlewares *Middleware
	Config      *Env
}

type shortLinkReq struct {
	URL                 string `json:"url" validate:"nonzero"`
	ExpirationInMinutes int64  `json:"expiration_in_minutes" validate:"min=0"`
}

type shortLinkResp struct {
	ShortLink string `json:"shortLink"`
}

//Init is initialization of App
func (a *App) Init(e *Env) {
	//set log formatter
	log.SetFlags(log.LstdFlags | log.Lshortfile)
	a.Router = mux.NewRouter()
	a.Middlewares = &Middleware{}
	a.Config = e
	a.initRoutes()

}

func (a *App) initRoutes() {
	//a.Router.Handle("/Api/ShortLink", a.Middlewares.LoggingHandler(a.Middlewares.RecoverHandler(
	//	http.HandlerFunc(a.createShortLink)))).Methods("POST")
	//a.Router.HandleFunc("/Api/Info", a.getShortLinkInfo).Methods("GET")
	//a.Router.HandleFunc("/{short_link:[a-zA-Z0-9]{1:11}}", a.redirect).Methods("GET")
	m := alice.New(a.Middlewares.LoggingHandler, a.Middlewares.RecoverHandler)
	a.Router.Handle("/Api/Shorten", m.ThenFunc(a.createShortLink)).Methods("POST")
	a.Router.Handle("/Api/Info", m.ThenFunc(a.getShortLinkInfo)).Methods("GET")
	a.Router.Handle("/{shortLink:[a-zA-Z0-9]{1,11}}", m.ThenFunc(a.redirect)).Methods("GET")
}

func (a *App) createShortLink(w http.ResponseWriter, r *http.Request) {
	req := new(shortLinkReq)
	if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
		respondWithError(w, StatusError{http.StatusBadRequest,
			fmt.Errorf("parse parameters failed! %+v", r.Body)})
		return
	}
	if err := validator.Validate(req); err != nil {
		respondWithError(w, StatusError{http.StatusBadRequest,
			fmt.Errorf("validate parameters failed! %+v", r.Body)})
		return
	}
	defer r.Body.Close()

	s, err := a.Config.S.Shorten(req.URL, req.ExpirationInMinutes)
	if err != nil {
		respondWithError(w, err)
	} else {
		respondWithJson(w, http.StatusCreated,
			map[string]interface{}{
				"code":    200,
				"message": "获取数据成功",
				"data":    shortLinkResp{ShortLink: s},
			})
	}
}

func (a *App) getShortLinkInfo(w http.ResponseWriter, r *http.Request) {
	vals := r.URL.Query()
	shortLink := vals.Get("short_link")
	if len(shortLink) < 1 {
		respondWithJson(w, http.StatusOK, map[string]interface{}{
			"code":    250,
			"message": "请求参数缺失",
			"data":    nil,
		})
		return
	}
	d, err := a.Config.S.ShortLinkInfo(shortLink)
	if err != nil {
		respondWithError(w, err)
	} else {
		respondWithJson(w, http.StatusOK, map[string]interface{}{
			"code":    200,
			"message": "获取数据成功",
			"data": map[string]interface{}{
				"detail": d,
			},
		})
	}
}

func (a *App) redirect(w http.ResponseWriter, r *http.Request) {
	vars := mux.Vars(r)
	u, err := a.Config.S.Unshorten(vars["shortLink"])
	if err != nil {
		respondWithError(w, err)
	} else {
		http.Redirect(w, r, u, http.StatusTemporaryRedirect) //临时重定向
	}

}

//Run starts listen and service
func (a *App) Run(addr string) {
	//启动一个协程ping本地启动服务端口
	go func() {
		for {
			time.Sleep(time.Second)
			log.Println("Checking if started...")
			resp, err := http.Get("http://127.0.0.1" + addr + "/Api/Info")
			if err != nil {
				log.Println("Failed:", err)
				continue
			}
			resp.Body.Close()
			if resp.StatusCode != http.StatusOK {
				log.Println("Not OK:", resp.StatusCode)
				continue
			}
			break
		}
		log.Println("短地址转换服务启动成功!端口", addr)
	}()

	log.Fatal(http.ListenAndServe(addr, a.Router))
}

func respondWithError(w http.ResponseWriter, err error) {
	switch e := err.(type) {
	case Error:
		log.Printf("HTTP %d - %v", e.Status(), e)
		respondWithJson(w, e.Status(), e.Error())
	default:
		respondWithJson(w, http.StatusInternalServerError,
			http.StatusText(http.StatusInternalServerError))
	}
}

func respondWithJson(w http.ResponseWriter, code int, payload interface{}) {
	resp, _ := jsoniter.MarshalToString(payload)

	w.Header().Set("Content-Type", "application/json")
	w.WriteHeader(code)
	w.Write([]byte(resp))
}
